2023-10-18
Use bash? Configure readline!
Annoyed by weird bash behaviour? Tempted to use another shell that offers fancy and shiny features? Then maybe the following tips and tricks will make bash more bearable for you. Turns out bash can behave nicely, you just have to tell it to.
Everyone knows the typical .bashrc
tricks, like fancy prompts,
aliases and functions.
Some may even know set -o vi
to enable vi mode (although I have
no idea why anyone would consider that useful).
Few people however seem to know that readline, the library used by bash and
a few other tools with REPL-style interfaces, like python, is configurable as well.
Much of the unexpected weirdness you'll encounter when working in bash
actually comes from readlines default settings.
Perhaps this arcane knowledge has fallen into oblivion because it is
canonically documented in GNU texinfo, which has a bit of a bad reputation.
The canonical path for the readline configuration is ~/.inputrc
.
Thankfully there is an env var to change that:
export INPUTRC="$XDG_CONFIG_HOME/inputrc"
My .profile
is full of lines like those.
What a time to be alive...
Anyway, let's go over my inputrc
line by line.
I don't use every possible setting and will explain only those I do use.
I'll start with general settings first.
At the end there is a spicy section for making completions even cooler.
General Settings
$include /etc/inputrc
This one is optional. It includes your systems default configuration (which may be in a different location on your system (nix users have no right to cry, they chose their pain voluntarily)). Some distributions are sane and change some readline settings, others don't. I am writing this on an arch box, which only sets a few input encoding related settings and enables a few keybinds for terminal emacs to use in the Linux and FreeBSD consoles.
set completion-ignore-case on
Arguable the most impactful setting I use. As the name suggest, it makes completion case insensitive. Here is how using it looks like (the black box is supposed to be the cursor):
$ ls vi *presses tab*
VID_20190720_154029.mp4 Video/ video-whatever.mp4
So much frustration prevented by just one line in a config file.
set completion-map-case on
This one is a tiny additon to the above option and only works if that one is
enabled as well.
It makes readline treat -
and _
as interchangeable
when searching for possible file completions.
set show-all-if-ambiguous on
set show-all-if-unmodified on
Has this ever happened to you: Type something in bash, press tab but nothing happens (except maybe a beep), press tab again and suddenly there are completion suggestions? That is what happens when these options are turned off. Turn them on and completions show up immediately on the first tab press without beeping (the actual explanation of these options is a bit more involved, but I chose to focus on the typical using experience).
set completion-query-items 0
Sometimes bash asks if you really want to see the completion because
there are just too many.
That might make sense over slow network connections (or more like bad phone
line connection back when readline was written), but I am not working remotely.
Setting this to zero will give readline consent always to dump all trauma
completions onto you, regardless of how many there are.
set page-completions off
If there are a lot of completions, too many to fit on your sad small terminal, they are paged and you have to press space to scroll through them all. Now, my terminal of choice happens to have a scrollback buffer, so I just turn this off.
set bell-style none
No more beeps.
You can also set it to visible
, but that doesn't do anything
in my terminal.
The default audible
also does nothing in my terminal, but I
set this option anyway for good measure.
set blink-matching-paren on
When writing braces or parenthesis, highlight the opening one when you close a pair.
set colored-completion-prefix on
When displaying completion suggestions, this will make readline highlight
the common prefix in a different colour.
The colour is configured using LS_COLORS
, another neat thing
people don't seem to know about...
set colored-stats on
When displaying completions suggestions which are files, this will make
readline highlight them using the same colours as in ls
.
Again, colours are configured using LS_COLORS
.
set completion-prefix-display-length 5
If the common prefix of completion suggestions is longer than the configured amount of characters, it will be replaced by ellipsis, like this:
$ ls IMG_20221717_ *hits tab*
...abd83e.mp4 ...f323c2.mp4 ...cc212ef.mp4
If you work with long file names often, this is a must have. Oddly sometimes it doesn't work though.
set history-size 5000
Selfexplanatory, I hope.
set horizontal-scroll-mode on
Ah, this is the one that might be a bit controversial. If you, like me, are annoyed by wrapping text, then you'll love this option. It makes readline just scroll horizontally on long input instead of wrapping to the next line.
set mark-directories on
Add /
to the names of directories in completion suggestions.
This is actually the default, but I faintly remember using a system where
this did not happen, so this is yet another option for good measure.
set skip-completed-text on
If triggering a completion while the cursor is in the middle of a word, this makes readline not insert characters that are already present on prompt.
I think this is better shown than explained. Here is what happens when this is turned off:
$ ls Makef *hits tab*
$ ls Makefilekef
And here what happens when it is turned on:
$ ls Makef *hits tab*
$ ls Makefile
There probably is some historic reason for the default, but it's weird to me that no system I have ever used has turned this on by default.
set visible-stats on
Certain file stats will cause certain characters to be appended to file names
in the completion suggestion display.
For example, executable files get a *
.
"\e[A": history-search-backward
"\e[B": history-search-forward
"\C-p": history-search-backward
"\C-n": history-search-forward
Have you ever looked at one of those modern shells, where you can type
the start of a command into the prompt and then just use the arrow keys
to cycle to all history entries mathing that prefix?
Did you then get jelous because bash, when scrolling through history, ignores
anything you have already written into the prompt and as such forces you to
use the C-r
mode if you want to search for something in the history?
Well, with these binds, bash now also has the fancy history scroll functionality.
Here both for the up / down arrow keys and also for C-n
plus
C-p
since that is exactly how completions work in kakoune,
which I have gotten very used to.
"\C-l": clear-display
By default, C-l
is bound to clear-screen
.
That clears the screen, however just moves everything into scrollback.
I perfer clearing the scrollback buffer as well, which clear-display
does.
Conditional Settings
You like some of these settings, but only want them to apply to the bash
prompt and not other readline based REPLs?
Fortunately, there are conditionals in the inputrc
syntax.
$if Bash
set bell-style audible
$end
Alternative Completion Paradigm
Ok, so this is how completion works in bash, usually: You type something, you press Tab and if there are multiple possible completions, they are printed below your prompt. The prompt is then duplicated below the suggestions.
$ ls co *smashes tab*
completion compare comedy contempt
$ ls co
If you want bash to pick one of the possible completions, you'll have to help it decide by adding more characters to your prompt. Then you'll have to press Tab again.
Readline also supports a different completion paradigm.
"\t": menu-complete
"\e[Z": menu-complete-backward
Now pressing Tab multiple times will cycle through all possible completions. With Shift-Tab you can cycle backwards. The list of possible completions is no longer dumped to the terminal.
You may also want this:
set menu-complete-display-prefix on
Now, this completion paradigm would be a bit too funky for me...
but here is where it gets cool:
If you additionaly enable the show-all-if-*
options from
earlier, the list of possible completions will be dumped to the terminal
even if menu-complete
is used.
Since the first press of Tab still does nothing (other than dumping the list),
enabling those options turns this completion method from a Very Different Thing™
into just a tiny enhancement to the default completion method.
It behaves like you are used to: Pressing Tab either completes until
there are no unambigous completions left, or dumps the list of possible
completion.
Now however, pressing tab again on an ambiguous prefix (or on an empty one)
will cycle through all possible completions.
Paste and Undo
These are part of the defaults, but watching people use bash without knowing about them is painful.
You'll hopefully already know about the basic readline editing commands:
C-backspace
or C-w
to delete entire words,
C-u
to kill from cursor to beginning of line, C-k
to kill from cursor to end of line, C-a
and C-e
to
quickly jump to the start / end of the line, M-f
and
M-b
to jump between words, etc..
However, did you know you that if you kill a text region, it gets copied
and that you can paste it with C-y
?
Want to undo an edit? Just press C-x C-u
.